Pandas、Numpy 性能优化秘籍
The following article is from 算法进阶 Author 泳鱼
1、NumExpr
NumExpr 是一个对NumPy计算式进行的性能优化。NumExpr的使用及其简单,只需要将原来的numpy语句使用双引号框起来,并使用numexpr中的evaluate方法调用即可。
经验上看,数据有上万条+ 使用NumExpr才比较优效果,对于简单运算使用NumExpr可能会更慢。如下较复杂计算,速度差不多快了5倍。
import numexpr as ne
import numpy as np
a = np.linspace(0,1000,1000)
print('# numpy十次幂计算')
%timeit a**10
print('# numexpr十次幂计算')
%timeit ne.evaluate('a**10')
2、Numba
# pip install numba
import numba as nb
# 用numba加速的求和函数
@nb.jit()
def nb_sum(a):
Sum = 0
for i in range(len(a)):
Sum += a[i]
return Sum
# 没用numba加速的求和函数
def py_sum(a):
Sum = 0
for i in range(len(a)):
Sum += a[i]
return Sum
import numpy as np
a = np.linspace(0,1000,1000) # 创建一个长度为1000的数组
print('# python求和函数')
%timeit sum(a)
print('# 没加速的for循环求和函数')
%timeit py_sum(a)
print('# numba加速的for循环求和函数')
%timeit nb_sum(a)
print('# numpy求和函数')
%timeit np.sum(a)
from numba import cuda
cuda.select_device(1)
@cuda.jit
def CudaSquare(x):
i, j = cuda.grid(2)
x[i][j] *= x[i][j]
#numba的矢量化加速
from math import sin
@nb.vectorize()
def nb_vec_sin(a):
return sin(a)
3、CuPy
CuPy 是一个借助 CUDA GPU 库在英伟达 GPU 上实现 Numpy 数组的库。基于 Numpy 数组的实现,GPU 自身具有的多个 CUDA 核心可以促成更好的并行加速。
# pip install cupy
import numpy as np
import cupy as cp
import time
### numpy
s = time.time()
x_cpu = np.ones((1000,1000,1000))
e = time.time()
print(e - s)
### CuPy
s = time.time()
x_gpu = cp.ones((1000,1000,1000))
e = time.time()
print(e - s)
4、pandas使用技巧
更多pandas性能提升技巧请戳官方文档:https://pandas.pydata.org/pandas-docs/stable/user_guide/enhancingperf.html
4.1 按行迭代优化
我们按行对dataframe进行迭代,一般我们会用iterrows这个函数。在新版的pandas中,提供了一个更快的itertuples函数,如下可以看到速度快了几十倍。
import pandas as pd
import numpy as np
import time
df = pd.DataFrame({'a': np.random.randn(100000),
'b': np.random.randn(100000),
'N': np.random.randint(100, 1000, (100000)),
'x': np.random.randint(1, 10, (100000))})
%%timeit
a2=[]
for row in df.itertuples():
temp=getattr(row, 'a')
a2.append(temp*temp)
df['a2']=a2
%%timeit
a2=[]
for index,row in df.iterrows():
temp=row['a']
a2.append(temp*temp)
df['a2']=a2
4.2 apply、applymap优化
当对于每行执行类似的操作时,用循环逐行处理效率很低。这时可以用apply或applymap搭配函数操作,其中apply是可用于逐行计算,而applymap可以做更细粒度的逐个元素的计算。
# 列a、列b逐行进行某一函数计算
df['a3']=df.apply( lambda row: row['a']*row['b'],axis=1)
# 逐个元素保留两位小数
df.applymap(lambda x: "%.2f" % x)
4.3 聚合函数agg优化
对于某列将进行聚合后,使用内置的函数比自定义函数效率更高,如下示例速度加速3倍
%timeit df.groupby("x")['a'].agg(lambda x:x.sum())
%timeit df.groupby("x")['a'].agg(sum)
%timeit df.groupby("x")['a'].agg(np.sum)
4.4 文件操作
4.5 pandas.eval
import pandas as pd
nrows, ncols = 20000, 100
df1, df2, df3, df4 = [pd.DataFrame(np.random.randn(nrows, ncols)) for _ in range(4)]
print('pd')
%timeit df1 + df2 + df3 + df4
print('pd.eval')
%timeit pd.eval("df1 + df2 + df3 + df4")
5、Cython优化
Cython是一个基于C语言的Python 编译器,在一些计算量大的程序中,可以Cython来实现相当大的加速。考虑大部分人可能都不太了解复杂的cython语句,下面介绍下Cython的简易版使用技巧。
通过在Ipython加入 Cython 魔术函数%load_ext Cython
,如下示例就可以加速了一倍。进一步再借助更高级的cython语句,还是可以比Python快个几十上百倍。
%%cython
def f_plain(x):
return x * (x - 1)
def integrate_f_plain(a, b, N):
s = 0
dx = (b - a) / N
for i in range(N):
s += f_plain(a + i * dx)
return s * dx
6、swifter
swifter是pandas的插件,可以直接在pandas的数据上操作。Swifter的优化方法检验计算是否可以矢量化或者并行化处理,以提高性能。如常见的apply就可以通过swifter并行处理。
import pandas as pd
import swifter
df.swifter.apply(lambda x: x.sum() - x.min())
7、Modin
Modin后端使用dask或者ray(dask是类似pandas库的功能,可以实现并行读取运行),是个支持分布式运行的类pandas库,简单通过更改一行代码import modin.pandas as pd
就可以优化 pandas,常用的内置的read_csv、concat、apply都有不错的加速。注:并行处理的开销会使小数据集的处理速度变慢。
!pip install modin
import pandas
import modin.pandas as pd
import time
## pandas
pandas_df = pandas.DataFrame({'a': np.random.randn(10000000),
'b': np.random.randn(10000000),
'N': np.random.randint(100, 10000, (10000000)),
'x': np.random.randint(1, 1000, (10000000))})
start = time.time()
big_pandas_df = pandas.concat([pandas_df for _ in range(25)])
end = time.time()
pandas_duration = end - start
print("Time to concat with pandas: {} seconds".format(round(pandas_duration, 3)))
#### modin.pandas
modin_df = pd.DataFrame({'a': np.random.randn(10000000),
'b': np.random.randn(10000000),
'N': np.random.randint(100, 10000, (10000000)),
'x': np.random.randint(1, 1000, (10000000))})
start = time.time()
big_modin_df = pd.concat([modin_df for _ in range(25)])
end = time.time()
modin_duration = end - start
print("Time to concat with Modin: {} seconds".format(round(modin_duration, 3)))
print("Modin is {}x faster than pandas at `concat`!".format(round(pandas_duration / modin_duration, 2)))
- EOF -
看完本文有收获?请转发分享给更多人
推荐关注「数据分析与开发」,提升数据技能
点赞和在看就是最大的支持❤️